#include "Shader.h"

// constructor
Shader::Shader(char* _vert_path, char* _frag_path)
{
	vert_path = _vert_path;
	frag_path = _frag_path;
	
	load();
}

Shader::~Shader()
{
	unbind(); // first make sure we unbind ourselves
	glDetachShader(shader_program, shader_vert);
	glDetachShader(shader_program, shader_frag);
	glDeleteShader(shader_vert);
	glDeleteShader(shader_frag);
	glDeleteProgram(shader_program);

	//delete [] vert_path;
	//delete [] frag_path;
}

void Shader::bind()
{
	glUseProgram(shader_program);
}

void Shader::unbind()
{
	glUseProgram(0);
}

void Shader::setUniform1(char* _id, int value)
{
	GLint location = glGetUniformLocation(shader_program, _id); 
	glUniform1i(location, value);
}

void Shader::setUniform1(char* _id, float value)
{
	GLint location = glGetUniformLocation(shader_program, _id); 
	glUniform1f(location, value);
}

void Shader::setUniform3(char* _id, float* value)
{
	GLint location = glGetUniformLocation(shader_program, _id); 
	glUniform3fv(location, 1, value);
}

void Shader::setUniformMatrix4fv(char* _id, int _count, bool _transpose, float* value)
{
	GLint location = glGetUniformLocation(shader_program, _id); 
	//glUniform4fv(location, value);
	glUniformMatrix4fv(location, _count, _transpose, value);
}

void Shader::load()
{
	printf("Shader: Loading '%s' and '%s'.\n", vert_path, frag_path);

	// Vertex and fragment shaders
	char *t_vert_content;
	t_vert_content = readShaderFile(vert_path);
	const char *t_vert_p = t_vert_content;
	shader_vert = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(shader_vert, 1, &t_vert_p, NULL);
	glCompileShader(shader_vert);
	delete [] t_vert_content;

	char *t_frag_content;
	t_frag_content = readShaderFile(frag_path);
	const char *t_frag_p = t_frag_content;
	shader_frag = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(shader_frag, 1, &t_frag_p, NULL);
	glCompileShader(shader_frag);
	delete [] t_frag_content;

	// Shader program
	shader_program = glCreateProgram();
	glAttachShader(shader_program, shader_vert);
	glAttachShader(shader_program, shader_frag);
	glLinkProgram(shader_program);
	
	// Print shader compile/link info
	printInfoLog(shader_vert,   false);
	printInfoLog(shader_frag,   false);
	printInfoLog(shader_program, true);
}

void Shader::reload()
{
	printf("Shader: Reloading '%s' and '%s'.\n", vert_path, frag_path);

	// Clean up
	unbind();
	glDetachShader(shader_program, shader_vert);
	glDetachShader(shader_program, shader_frag);

	// Vertex and fragment shaders
	char *t_vert_content;
	t_vert_content = readShaderFile(vert_path);
	const char *t_vert_p = t_vert_content;
	glShaderSource(shader_vert, 1, &t_vert_p, NULL);
	glCompileShader(shader_vert);
	delete [] t_vert_content;

	char *t_frag_content;
	t_frag_content = readShaderFile(frag_path);
	const char *t_frag_p = t_frag_content;
	glShaderSource(shader_frag, 1, &t_frag_p, NULL);
	glCompileShader(shader_frag);
	delete [] t_frag_content;

	// Shader program
	glAttachShader(shader_program, shader_vert);
	glAttachShader(shader_program, shader_frag);
	glLinkProgram(shader_program);
	
	// Print shader compile/link info
	printInfoLog(shader_vert,   false);
	printInfoLog(shader_frag,   false);
	printInfoLog(shader_program, true);
}

// privates
char* Shader::readShaderFile(char *path)
{
	FILE *fp;
	char *content = NULL;

	int count=0;

	if (path != NULL) {
		fp = fopen(path,"rt");

		if (fp != NULL) {
      
      fseek(fp, 0, SEEK_END);
      count = ftell(fp);
      rewind(fp);

			if (count > 0) {
				content = new char[count+1];
				count = fread(content,sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}
	return content;
}

void Shader::printInfoLog(GLuint obj, bool prg)
{
	int infologLength = 0;
	int compile_link_status = 1;
	int charsWritten  = 0;
	char *infoLog;


	if (prg == true)
	{
		glGetProgramiv(obj, GL_LINK_STATUS, &compile_link_status);
		if (compile_link_status == 0)
			printf("Shader: Link error!\n");
	} else {
		glGetShaderiv(obj, GL_COMPILE_STATUS, &compile_link_status);
		if (compile_link_status == 0)
			printf("Shader: Compile error!\n");
	}



	if (compile_link_status == 1)
		return;

	if (prg == true)
		glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
	else
		glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
	
	if (infologLength > 0)
	{
		infoLog = new char[infologLength];
		if (prg == 1)
			glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		else
			glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
		delete infoLog;
	}
}